package com.sylli.oeuf.server.game.logic;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.Map.Entry;

import com.sylli.oeuf.server.game.logic.Clazz.ClazzId;
import com.sylli.oeuf.server.game.logic.invocation.InvocationEvent;
import com.sylli.oeuf.server.game.logic.invocation.InvocationParameter;
import com.sylli.oeuf.server.game.logic.invocation.LogicObjectFactory;
import com.sylli.oeuf.server.object.transferable.CharacterGeneralInfoTO;
import com.sylli.oeuf.server.object.transferable.LevelUpTO;
import com.sylli.oeuf.server.object.transferable.MonsterTO;
import com.sylli.oeuf.server.object.transferable.PostBattleResponseTO;
import com.sylli.oeuf.server.object.transferable.SkillTO;
import com.sylli.oeuf.server.object.transferable.StatTO;
import com.sylli.oeuf.server.object.transferable.WrappedCharacterInfoTO;

public class BattleActor {
	
	private static Leveling logic = new LevelLogic();

	public enum Type {Monster, Player};
	
	private Type type;
	
	private ClazzId clazzId = ClazzId.NULL;
	
	private String name;
	
	private int currHealth;
	
	private int maxHealth;
	
	private int currMana;
	
	private int maxMana;

	private int maxDmg;
	
	private int minDmg;
	
	private StatTO baseStat;
	
	private StatTO modifiedStat;
	
	private LevelUpTO levelUp;
	
	private Plan usingPlan;
	
	private BattleActor focusTarget;
	
	private BattleActorSource source;
	
	private BattleEventDispatcher dispatcher;
	
	private Hashtable<String, Buff> buff = new Hashtable<String, Buff>();
	
	private Hashtable<String, DeBuff> debuff = new Hashtable<String, DeBuff>();
	
	public void newTurn() {
		// refresh stat.
		modifiedStat = baseStat;
	}
	
	public void recompute() {
		this.maxDmg = logic.getMaxDmgFromStat(modifiedStat, clazzId);
		this.minDmg = logic.getMinDmgFromStat(modifiedStat, clazzId);
	}
	
	public boolean hasDeBuff(String debuffName) {
		return this.debuff.keySet().contains(debuffName);
	}
	
	public boolean hasBuff(String buffName) {
		return this.buff.keySet().contains(buffName);
	}
	
	public BattleActor(BattleActorSource source) {
		this.source = source;
	}
	
	public void reverseSnap() {
		this.source.snapback(this);
	}
	
	/**
	 * 
	 * @dispatch ADD_BUFF event.
	 * @param b
	 */
	protected void API__addBuff(Buff b, InvocationParameter param) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_ADD_DEBUFF, this, b);
		buff.put(b.getName(), b);
		dispatcher.bindSkill(b, param);
	}
	
	/**
	 * 
	 * @dispatch ADD_DEBUFF event.
	 * @param b
	 */
	protected void API__addDeBuff(DeBuff db, InvocationParameter param) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_ADD_DEBUFF, this, db);
		debuff.put(db.getName(), db);
		dispatcher.bindSkill(db, param);
	}
	
	/**
	 * 
	 * @dispatch REMOVE_DEBUFF event.
	 * @param db
	 */
	protected void API__removeDeBuff(DeBuff db) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_REMOVE_DEBUFF, this, db);
		debuff.remove(db.getName());
		dispatcher.unbindSkill(db);
	}
	

	/**
	 * 
	 * @dispatch REMOVE_BUFF event.
	 * @param value
	 */
	protected void API__removeBuff(Buff b) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_REMOVE_BUFF, this, b);
		buff.remove(b.getName());
		dispatcher.unbindSkill(b);
	}
	
	/**
	 * 
	 * @dispatch MODIFY_HP event.
	 * @param value
	 */
	protected void API__modifyHp(int value) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_MODIFY_HP, this);
		currHealth += value;
		if (currHealth > maxHealth) {
			currHealth = maxHealth;
		} else if (currHealth < 0) {
			currHealth = 0;
		}
	}
	
	/**
	 * 
	 * @dispatch MODIFY_MP event.
	 * @param value
	 */
	protected void API__modifyMp(int value) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_MODIFY_MP, this);
		currMana += value;
		if (currMana > maxMana) {
			currMana = maxMana;
		} else if (currMana < 0) {
			currMana = 0;
		}
	}
	
	/**
	 * 
	 * @dispatch MODIFY_STAT.
	 * @param value
	 */
	protected void API__modifyStat(StatTO value) {
		dispatcher.dispatchEvent(InvocationEvent.STD_IE_ON_MODIFY_STAT, this);
		modifiedStat = this.modifiedStat.sum(value);
	}
	
	public void decayBuffAndDebuff() {
		Set<String> ss = new HashSet<String>();
		ss.clear();
		for (Entry<String,Buff> b : buff.entrySet()) {
			b.getValue().decay();
			if (b.getValue().isDepleted()) {
				ss.add(b.getKey());
			}
		}
		for (String s : ss) {
			buff.remove(s);
		}
		
		ss.clear();
		for (Entry<String,DeBuff> b : debuff.entrySet()) {
			b.getValue().decay();
			if (b.getValue().isDepleted()) {
				ss.add(b.getKey());
			}
		}
		for (String s : ss) {
			debuff.remove(s);
		}
	}
	
	public BattleActor getProperTarget(CastingTarget target) {
		return (target.equals(CastingTarget.FocusTarget))?focusTarget:this;
	}
	
	public void dispatchEvent(InvocationEvent event, EventParameter param) {
		dispatcher.dispatchEvent(event, this, param);
	}
	
	public boolean isDefeated() {
		return currHealth <= 0;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public void initializeActor(PostBattleResponseTO sharedCombatProduct, BattleActor focusTarget) {
		dispatcher = new BattleEventDispatcher(sharedCombatProduct);
		this.focusTarget = focusTarget;
		if (source instanceof WrappedCharacterInfoTO) {
			WrappedCharacterInfoTO obj = (WrappedCharacterInfoTO) source;

			CharacterGeneralInfoTO gen = obj.getGeneralInfo();
			
			this.clazzId = Clazz.getClazzFromId(gen.getClazzId());
			this.name = gen.getName();
			this.currHealth = obj.getPostData().getCurrentHp();
			this.currMana = obj.getPostData().getCurrentMp();
			this.maxHealth = gen.getMaxHp();
			this.maxMana = gen.getMaxMp();
			this.baseStat = gen.getStat();
			this.maxDmg = logic.getMaxDmgFromStat(baseStat, clazzId);
			this.minDmg = logic.getMinDmgFromStat(baseStat, clazzId);
			for (SkillTO s : obj.getGeneralInfo().getSkillBundle().getBundle()) {
				if (s.getRank()==0) {
					continue;
				}
				Skill _s = LogicObjectFactory.getSkillByName(s.getSkillDbName());
				// install passive skills (from skillbundle)
				if (_s instanceof PassiveSkill) {
					dispatcher.addDispatcherPassiveSkillEntry(_s, s.getRank());
				}
			}
			this.type = Type.Player;
			
		} else if (source instanceof MonsterTO) {
			MonsterTO mob = (MonsterTO) source;
			
			this.clazzId = ClazzId.NULL;
			this.name = mob.getName();
			this.currHealth = mob.getStatHp();
			this.maxHealth = mob.getStatHp();
			this.currMana = mob.getStatMp();
			this.maxMana = mob.getStatMp();
			this.maxDmg = (int) (mob.getStat().getStatStr() * 1.3);
			this.minDmg = mob.getStat().getStatStr();
			this.baseStat = mob.getStat();
			this.type = Type.Monster;
		}
		// install active skills (from strategy)
		dispatcher.addDispatcherEntry(usingPlan);
	}
	
	public int getCurrHealth() {
		return currHealth;
	}

	public void setCurrHealth(int currHealth) {
		this.currHealth = currHealth;
	}

	public int getMaxHealth() {
		return maxHealth;
	}

	public void setMaxHealth(int maxHealth) {
		this.maxHealth = maxHealth;
	}

	public int getCurrMana() {
		return currMana;
	}

	public void setCurrMana(int currMana) {
		this.currMana = currMana;
	}

	public int getMaxMana() {
		return maxMana;
	}

	public void setMaxMana(int maxMana) {
		this.maxMana = maxMana;
	}

	public int getMaxDmg() {
		return maxDmg;
	}

	public void setMaxDmg(int maxDmg) {
		this.maxDmg = maxDmg;
	}

	public int getMinDmg() {
		return minDmg;
	}

	public void setMinDmg(int minDmg) {
		this.minDmg = minDmg;
	}

	public BattleEventDispatcher getDispatcher() {
		return dispatcher;
	}

	public void setDispatcher(BattleEventDispatcher dispatcher) {
		this.dispatcher = dispatcher;
	}

	public Type getType() {
		return type;
	}

	public void setType(Type type) {
		this.type = type;
	}

	public Plan getUsingPlan() {
		return usingPlan;
	}

	public void setUsingPlan(Plan usingPlan) {
		this.usingPlan = usingPlan;
	}

	public BattleActorSource getSource() {
		return source;
	}

	public void setSource(BattleActorSource source) {
		this.source = source;
	}
	
	public StatTO getModifiedStat() {
		return modifiedStat;
	}

	public void setModifiedStat(StatTO modifiedStat) {
		this.modifiedStat = modifiedStat;
	}

	public StatTO getBaseStat() {
		return baseStat;
	}

	public void setBaseStat(StatTO baseStat) {
		this.baseStat = baseStat;
	}

	public LevelUpTO getLevelUp() {
		return levelUp;
	}

	public void setLevelUp(LevelUpTO levelUp) {
		this.levelUp = levelUp;
	}

	public ClazzId getClazzId() {
		return clazzId;
	}

	public void setClazzId(ClazzId clazzId) {
		this.clazzId = clazzId;
	}
}
